classdef HorseRace
    %This class executes a horse race between various trading strategies.
    %This class takes the list of trading strategies to be executed as an input,
    %and runs a horse race between all trading strategies in the list. 
    %It can also output tables for the performance of various strategies in the horse race,
    %as well as their risk prediction characteristics.
    
    %This structure also makes it convenient to add new trading strategies
    %in the horse race without changing the code for the horse race. 
    %The only change will be in the list of trading strategies fed to this class.
        
    properties
        performanceResults
        riskResults
        performanceRiskResults
        hitpvals
        tradingStrategyArray
        tradingStrategyNames  
        learning_window; %The window for Galton to pre-learn and others to skip
        pre_window; %The estimation window
        post_window; %The period of the subsequent realized window
        rf;
        %externalRiskAnalysisFlag=false;
        start_loc;
        nstrats;

    end
    
    methods
        
        function obj = chooseHorses(obj,tsArray)
            obj.tradingStrategyArray = tsArray;
            obj.nstrats = max(size(obj.tradingStrategyArray));
        end
        
         function [ obj ] = runHorserace(obj,eret,ids)

            
            T=size(eret,2);
            
            obj.start_loc = obj.learning_window + obj.pre_window + obj.post_window + 1;

            for i = 1:obj.nstrats   
                   obj.tradingStrategyArray(i).ids = ids;  
                   obj.tradingStrategyArray(i).weights = NaN(size(ids));
                   obj.tradingStrategyArray(i).strategyReturns = NaN(T,1);
                   obj.tradingStrategyArray(i).strategyExpectedReturns = NaN(T,1);
                   obj.tradingStrategyArray(i).strategyExpectedVariance = NaN(T,1);
                   obj.tradingStrategyArray(i).rf = obj.rf;
            end

            for t=obj.pre_window:T-obj.post_window+1
            
                if nargin == 3
                    chosen_stocks = ids(:,t);                   
                end
                        
                if t>= obj.start_loc    %The horse races start after the burn-in period for Galton, plus the first estimation (pre_window) period

                    r_past=eret(chosen_stocks,t-obj.pre_window+1:t)';
                    r_plus1=eret(chosen_stocks,t+1);
                    tot_r_last = eret(ids(:,t-1),t)+obj.rf(t);
                    
                    for i = 1:obj.nstrats 
                            %Each trading strategy class in the horse race
                            %inherts from the TradingStrategy interface.
                            %This interface forces each strategy to specify a "strategyCalculate()" function.
                            %Without knowing which trading strategy is
                            %passed to it, the horce race calls this
                            %function. All instructions outside the
                            %tradingStrategyArray(i) can be the same for
                            %all strategies using this program design.
                          
                            %The first argument 't' is the time period for
                            %evaluating the strategy OOS
                            %The second argument passes the historical
                            %pre_window excess returns used to construct strategy weights.
                            %The third argument passes the OOS excess returns to be evaluated.
                            %The fourth argument passes the latest return
                            %(adding rf to excess return), mainly to
                            %calculate the most recent inertia of the strategy.
                          
                            obj.tradingStrategyArray(i) = obj.tradingStrategyArray(i).strategyCalculate(t,r_past,r_plus1,tot_r_last);
                    end
                end                                
            end
            
            
            
         end
                 
        
        function [ obj ] = computeHorseraceResults(obj)
            %The comparison across strategies is done after all strategies
            %are executed for the entire evaluation period 
            obj.tradingStrategyNames = cell(obj.nstrats, 20);
            for i = 1:obj.nstrats
                        obj.performanceResults(i,:) = obj.tradingStrategyArray(i).summary(obj.start_loc);
                        [obj.riskResults(i,:), obj.hitpvals(i,:)] = obj.tradingStrategyArray(i).riskSummary(obj.start_loc);
                        obj.performanceRiskResults(i,:) = obj.tradingStrategyArray(i).performanceRiskSummary(obj.start_loc);
                        obj.tradingStrategyNames(i,:) = obj.tradingStrategyArray(i).name;         
            end

        end
        
       function [ obj ] = setTransactionCosts(obj,transactionCostsParam)
         
            for i = 1:obj.nstrats   
                   obj.tradingStrategyArray(i).transactionCosts = transactionCostsParam;
                   
                   if ~isnan(transactionCostsParam)
                       obj.tradingStrategyArray(i).TransactionCostOptimize=true;
                   else
                       obj.tradingStrategyArray(i).TransactionCostOptimize=false;
                   end
            end            
            
       end
       
       function [ obj ] = setOOSMomentsForPredictions(obj,eret,ids)
            
            T=size(eret,2);            
            obj.start_loc = obj.learning_window + obj.pre_window + obj.post_window + 1;
 
            for t=obj.start_loc:12:T-obj.post_window           
                
                    %r_plus_12 = NaN(size(eret(ids(:,t),t+1),1),12);
                    
%                     for delta=1:12 
%                         r_plus_12(:,delta)=eret(ids(:,t+delta-1),t+delta);
%                     end
                    %for delta=1:12 
                    %if sum(ids(:,t-1)-ids(:,t-2)) > 0
                    r_plus=eret(ids(:,t),t+1:t+12);
                    %end
                    %end
                    futureMeans = nanmean(r_plus,2)';
                    futureCov = nancov(r_plus');              
            
                    
                    for i = 1:obj.nstrats   
                           obj.tradingStrategyArray(i).OOSMeansForPrediction(t+1,:) = futureMeans; 
                           obj.tradingStrategyArray(i).OOSCovForPrediction(t+1,:,:) = futureCov; 
                    end
                
            end
            
            for i = 1:obj.nstrats   
                       obj.tradingStrategyArray(i).storeMoments=true; 
            end

       end
       
       function [ obj ] = setExternalRiskAnalysis(obj,index)

            riskCovs = obj.tradingStrategyArray(index).Covs;
            riskMeans = obj.tradingStrategyArray(index).Means;
             
            disp("Setting expectations based on ");
            celldisp(obj.tradingStrategyArray(index).name)
            fprintf("The strategy index number supplied is: %0d \n", index);
           for i = 1:obj.nstrats  
                     obj.tradingStrategyArray(i) = obj.tradingStrategyArray(i).resetExpectations(riskCovs,riskMeans);
                     [obj.riskResults(i,:), obj.hitpvals(i,:)] = obj.tradingStrategyArray(i).riskSummary(obj.start_loc+1);
           end
            
       end
       function [ obj ] = setExternalMoments(obj,index)

            extCovs = obj.tradingStrategyArray(index).Covs;
            extMeans = obj.tradingStrategyArray(index).Means;
             
            disp("Setting Moments based on ");
            celldisp(obj.tradingStrategyArray(index).name)
            fprintf("The strategy index number supplied is: %0d \n", index);
           for i = 1:obj.nstrats  
                     obj.tradingStrategyArray(i).externalMoments = true;
                     obj.tradingStrategyArray(i).externalCovs = extCovs;
                     obj.tradingStrategyArray(i).externalMeans = extMeans;
           end
            
       end
               
       function outputPerformanceResults(obj,stats)
            tab_perf_out = obj.performanceResults(:,STATISTIC_MAP( stats ));
           % latexOutput(tab_perf_out,zeros(1,size(tab_perf_out,1)),obj.tradingStrategyNames,'Table : OOS performance');
        end
        
        function outputRiskResults(obj)
            [N, K] = size(obj.riskResults);
            labels = cell(N*2, 20);
            
            tab_risk_out = NaN(2*N,K);
            labels(1:2:2*N-1,:) = obj.tradingStrategyNames;
            labels(2:2:2*N,:) = {'p-value'};
            
            tab_risk_out(1:2:2*N-1,:) = obj.riskResults*100;
            tab_risk_out(2:2:2*N,:) = obj.hitpvals;
            brackets = zeros(1,size(tab_risk_out,1));
            brackets(2:2:end) = 1;
         %   latexOutput(tab_risk_out,brackets,labels(:,1),'Table : expectation vs realization');
        end
        
        function outputPerformanceRiskResults(obj)
            
            tab_perf_out = obj.performanceRiskResults;
            latexOutput(tab_perf_out,zeros(1,size(tab_perf_out,1)),obj.tradingStrategyNames,'Table : OOS performance and risk summary');            
            
        end

     end

        
    
    
end

